from flask import Flask, Response, request, jsonify, render_template, redirect, url_for, session, send_from_directory, stream_with_context, send_file
from flask_sslify import SSLify
from PIL import ImageGrab
from PIL import Image, ImageDraw
import io
import time
import threading
from threading import Thread
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
import pystray
import subprocess
import pyautogui
import os
import pyperclip

app = Flask(__name__)
app.secret_key = os.urandom(24).hex()
sslify = SSLify(app)

# 全局变量
try:
    with open('config.txt','r') as config_file:
        config_text = config_file.read()
    exec(config_text)
except BaseException:
    fps = 30
    compression_quality = 10
    ipv4 = False
    enable_simulation = False
    users = ['', '']
    ipport = 54250
    enable_video = True
    enable_file_download = True
    enable_file_upload = False
    UPLOAD_FOLDER = ''

    with open('config.txt','w') as config_file:
        config_file.write('''
fps = 30
compression_quality = 10
ipv4 = False
enable_simulation = False
users = ['', '']
ipport = 54250
enable_video = True
enable_file_download = True
enable_file_upload = False
UPLOAD_FOLDER = ''
        '''.strip())

# 不可配置全局变量
ip = ''
screen_width, screen_height = pyautogui.size()
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
sleeptime = 0.033

def gen():
    while True:
        img_byte_arr = io.BytesIO()
        screen = ImageGrab.grab()
        screen.save(img_byte_arr, format='JPEG', quality=compression_quality)
        frame = img_byte_arr.getvalue()
        screen.close()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
        time.sleep(sleeptime)

# 视频帧
@app.route('/video_feed')
def video_feed():
    return Response(gen(), mimetype='multipart/x-mixed-replace; boundary=frame')

# 普通点击映射（依然保留，可供单击使用）
@app.route('/click', methods=['POST'])
def handle_click():
    if not enable_simulation or ('username' not in session):
        return jsonify(status='disabled')
    
    data = request.json
    x_percent = data['x']  # 百分比坐标
    y_percent = data['y']
    button = data.get('button', 'left')

    # 将百分比转换为实际坐标
    actual_x = int((x_percent / 100) * screen_width)
    actual_y = int((y_percent / 100) * screen_height)

    if button == 'right':
        pyautogui.rightClick(actual_x, actual_y)
    else:
        pyautogui.click(actual_x, actual_y)
    
    return jsonify(status='success')

# 新增拖动映射，服务端只根据拖动起始和结束坐标模拟一次快速直线拖动
@app.route('/drag', methods=['POST'])
def handle_drag():
    if not enable_simulation or ('username' not in session):
        return jsonify(status='disabled')
    
    data = request.json
    start_x_percent = data['start_x']
    start_y_percent = data['start_y']
    end_x_percent = data['end_x']
    end_y_percent = data['end_y']
    button = data.get('button', 'left')
    
    # 将百分比转换为实际坐标
    actual_start_x = int((start_x_percent / 100) * screen_width)
    actual_start_y = int((start_y_percent / 100) * screen_height)
    actual_end_x = int((end_x_percent / 100) * screen_width)
    actual_end_y = int((end_y_percent / 100) * screen_height)
    
    # 移动到起始位置
    pyautogui.moveTo(actual_start_x, actual_start_y)
    # 按下鼠标按钮
    pyautogui.mouseDown(button=button)
    # 保持按下状态 0.3 秒
    time.sleep(0.3)
    # 快速直线拖动到目标位置（可根据需要调整拖动时长）
    pyautogui.moveTo(actual_end_x, actual_end_y, duration=0.05)
    # 释放鼠标按钮
    pyautogui.mouseUp(button=button)
    
    return jsonify(status='success')

# 按键映射
@app.route('/keypress', methods=['POST'])
def handle_keypress():
    if not enable_simulation or ('username' not in session):
        return jsonify(status='disabled')
    
    data = request.json
    key = data['key']
    pyautogui.press(key)
    return jsonify(status='success')

# 共享屏幕主页面（客户端页面）
index_html = '''
<html>
    <head>
        <style>
            body, html {
                margin: 0;
                padding: 0;
                width: 100%;
                height: 100%;
                display: flex;
            }
            #click-area {
                flex-grow: 1;
                border: 1px solid black;
                position: relative;
                overflow: hidden;
            }
            img {
                width: 100%;
                height: 100%;
                object-fit: contain;
            }
            #floating-button {
                position: absolute;
                top: 20px;
                left: 20px;
                width: 50px;
                height: 50px;
                background-color: #39c5bb;
                color: white;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                user-select: none;
            }
            #floating-window {
                position: absolute;
                top: 80px;
                left: 20px;
                width: 200px;
                background-color: white;
                border: 1px solid #ccc;
                border-radius: 5px;
                padding: 10px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
                display: none;
            }
            #floating-window label {
                display: block;
                margin-bottom: 10px;
            }
            .window-button {
                width: 100%;
                padding: 8px;
                margin-bottom: 10px;
                background-color: #39c5bb;
                color: white;
                border: none;
                border-radius: 3px;
                cursor: pointer;
                text-align: center;
                transition: background-color 0.3s;
            }
            .window-button:hover {
                background-color: #2da49c;
            }
        </style>
    </head>
    <body>
        <div id="click-area">
            <img src="/video_feed" />
        </div>
        <div id="floating-button">⚙️</div>
        <div id="floating-window">
            <label>
                <input type="checkbox" id="toggle-right-click"> 左键变右键
            </label>
            <button class="window-button" id="file-manager-btn">文件管理</button>
        </div>
        <script>
            const clickArea = document.getElementById('click-area');
            const floatingButton = document.getElementById('floating-button');
            const floatingWindow = document.getElementById('floating-window');
            const toggleRightClick = document.getElementById('toggle-right-click');
            const fileManagerBtn = document.getElementById('file-manager-btn');

            // 文件管理按钮点击事件 - 跳转到当前URL的/file路径
            fileManagerBtn.addEventListener('click', () => {
                // 获取当前URL并添加/file路径
                const currentUrl = window.location.href;
                // 处理可能已存在的路径
                const baseUrl = currentUrl.split('/').slice(0, 3).join('/');
                const fileUrl = `${baseUrl}/file`;
                window.location.href = fileUrl;
            });

            let isDragging = false;
            let offsetX, offsetY;

            // 屏蔽右键默认菜单
            clickArea.addEventListener('contextmenu', (event) => {
                event.preventDefault();
            });

            // 悬浮按钮点击事件
            floatingButton.addEventListener('click', () => {
                floatingWindow.style.display = floatingWindow.style.display === 'none' ? 'block' : 'none';
            });

            // 悬浮按钮拖动事件
            floatingButton.addEventListener('mousedown', (event) => {
                isDragging = true;
                offsetX = event.clientX - floatingButton.offsetLeft;
                offsetY = event.clientY - floatingButton.offsetTop;
            });

            document.addEventListener('mousemove', (event) => {
                if (isDragging) {
                    floatingButton.style.left = `${event.clientX - offsetX}px`;
                    floatingButton.style.top = `${event.clientY - offsetY}px`;
                    floatingWindow.style.left = `${event.clientX - offsetX}px`;
                    floatingWindow.style.top = `${event.clientY - offsetY + 60}px`;
                }
            });

            document.addEventListener('mouseup', () => {
                isDragging = false;
            });

            // 根据图片的渲染尺寸计算有效点击区域，并返回百分比坐标
            function getImageCoordinates(event) {
                const img = document.querySelector('img');
                const rect = img.getBoundingClientRect();
                const naturalWidth = img.naturalWidth;
                const naturalHeight = img.naturalHeight;
                const displayWidth = rect.width;
                const displayHeight = rect.height;
                
                const imgAspectRatio = naturalWidth / naturalHeight;
                const displayAspectRatio = displayWidth / displayHeight;
                
                let renderWidth, renderHeight, offsetX, offsetY;
                
                if (displayAspectRatio > imgAspectRatio) {
                    renderHeight = displayHeight;
                    renderWidth = renderHeight * imgAspectRatio;
                    offsetX = (displayWidth - renderWidth) / 2;
                    offsetY = 0;
                } else {
                    renderWidth = displayWidth;
                    renderHeight = renderWidth / imgAspectRatio;
                    offsetX = 0;
                    offsetY = (displayHeight - renderHeight) / 2;
                }
                
                const x = event.clientX - rect.left - offsetX;
                const y = event.clientY - rect.top - offsetY;
                
                if (x < 0 || x > renderWidth || y < 0 || y > renderHeight) {
                    return null;
                }
                
                const xPercent = (x / renderWidth) * 100;
                const yPercent = (y / renderHeight) * 100;
                
                return { xPercent, yPercent };
            }
            
            // 用于记录鼠标按下时的起始坐标
            let dragStart = null;
            
            // 鼠标按下时记录起始坐标（不立即发送点击指令）
            clickArea.addEventListener('mousedown', (event) => {
                event.preventDefault();
                const coords = getImageCoordinates(event);
                if (!coords) return;
                const button = toggleRightClick.checked ? 'right' : (event.button === 2 ? 'right' : 'left');
                dragStart = { x: coords.xPercent, y: coords.yPercent, button };
            });
            
            // 鼠标松开时，获取结束坐标，并发送一次拖动请求到服务端
            clickArea.addEventListener('mouseup', (event) => {
                event.preventDefault();
                if (!dragStart) return;
                const coords = getImageCoordinates(event);
                if (!coords) {
                    dragStart = null;
                    return;
                }
                fetch('/drag', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        start_x: dragStart.x,
                        start_y: dragStart.y,
                        end_x: coords.xPercent,
                        end_y: coords.yPercent,
                        button: dragStart.button
                    }),
                });
                dragStart = null;
            });
            
            // 键盘事件映射
            document.addEventListener('keydown', (event) => {
                const key = event.key;
                fetch('/keypress', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ key }),
                });
            });
            
            // 窗口大小变化时重置图片尺寸
            window.addEventListener('resize', () => {
                const img = document.querySelector('img');
                if (img) {
                    img.style.width = '100%';
                    img.style.height = '100%';
                }
            });
        </script>
    </body>
</html>

'''
@app.route('/')
def index():
    if 'username' in session:
        if enable_video == True:
            return index_html
        else:
            return redirect(url_for('fileindex'))
    else:
        return redirect('/login')

# 登录页面
@app.route('/login', methods=['GET', 'POST'])
def login():
    error_message = ""
    if request.method == 'POST':
        username = request.form['账户名']
        password = request.form['密码']
        
        if username == users[0] and password == users[1]:
            session['username'] = username
            return redirect(url_for('index'))
        else:
            error_message = "用户名或密码错误，请重新输入"
    
    return f'''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户登录</title>
    <style>
        /* 基础样式重置 */
        * {{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        }}
        
        /* 页面布局 */
        body {{
            background-color: #f0f2f5;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }}
        
        /* 登录容器 */
        .login-container {{
            width: 100%;
            max-width: 400px;
        }}
        
        /* 登录卡片 */
        .login-card {{
            background-color: white;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            padding: 30px;
            transition: box-shadow 0.3s ease;
        }}
        
        .login-card:hover {{
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
        }}
        
        /* 标题样式 */
        .login-title {{
            text-align: center;
            font-size: 24px;
            font-weight: 600;
            color: #1a1a1a;
            margin-bottom: 25px;
        }}
        
        /* 错误提示样式 */
        .error-message {{
            background-color: #fff0f0;
            border: 1px solid #ffcccc;
            color: #d8000c;
            padding: 12px 15px;
            border-radius: 6px;
            margin-bottom: 20px;
            font-size: 14px;
            display: { 'block' if error_message else 'none' };
        }}
        
        /* 表单样式 */
        .login-form {{
            display: flex;
            flex-direction: column;
            gap: 20px;
        }}
        
        /* 表单组样式 */
        .form-group {{
            display: flex;
            flex-direction: column;
            gap: 8px;
        }}
        
        /* 标签样式 */
        .form-label {{
            font-size: 14px;
            font-weight: 500;
            color: #4b4b4b;
        }}
        
        /* 输入框样式 */
        .form-input {{
            width: 100%;
            padding: 12px 15px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 16px;
            transition: all 0.2s ease;
        }}
        
        .form-input:focus {{
            border-color: #165DFF;
            box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
            outline: none;
        }}
        
        /* 按钮样式 */
        .submit-btn {{
            width: 100%;
            padding: 12px;
            background-color: #165DFF;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.3s ease;
        }}
        
        .submit-btn:hover {{
            background-color: #0E4BDB;
            box-shadow: 0 4px 8px rgba(22, 93, 255, 0.2);
        }}
    </style>
</head>
<body>
    <div class="login-container">
        <div class="login-card">
            <h2 class="login-title">登录</h2>
            
            <!-- 错误提示区域 -->
            <div class="error-message">{error_message}</div>
            
            <form method="post" class="login-form">
                <div class="form-group">
                    <label class="form-label">账户名:</label>
                    <input type="text" name="账户名" class="form-input" placeholder="请输入账户名">
                </div>
                
                <div class="form-group">
                    <label class="form-label">密码:</label>
                    <input type="password" name="密码" class="form-input" placeholder="请输入密码">
                </div>
                
                <input type="submit" value="登录" class="submit-btn">
            </form>
        </div>
    </div>
</body>
</html>
    '''

# 文件传输页面
HTML = '''
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件管理器</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: #333;
            max-width: 800px;
            margin: 0 auto;
            padding: 30px 20px;
            background-color: #f8f9fa;
        }

        /* 返回按钮样式 */
        .back-button {
            position: absolute;
            top: 30px;
            left: 20px;
            background-color: #95a5a6;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.9rem;
            text-decoration: none;
            transition: background-color 0.3s ease;
        }

        .back-button:hover {
            background-color: #7f8c8d;
        }

        h1 {
            color: #2c3e50;
            margin-bottom: 30px;
            padding-bottom: 10px;
            border-bottom: 2px solid #3498db;
            text-align: center;
        }

        h2 {
            color: #34495e;
            margin: 25px 0 15px;
            font-size: 1.4rem;
        }

        .upload-form {
            background-color: white;
            padding: 25px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            margin-bottom: 30px;
            transition: box-shadow 0.3s ease;
        }

        .upload-form:hover {
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
        }

        input[type="file"] {
            display: block;
            margin-bottom: 15px;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            width: 100%;
            cursor: pointer;
        }

        input[type="submit"] {
            background-color: #3498db;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
            transition: background-color 0.3s ease;
        }

        input[type="submit"]:hover {
            background-color: #2980b9;
        }

        ul {
            list-style-type: none;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }

        .file-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 12px 20px;
            border-bottom: 1px solid #f1f1f1;
        }

        .file-link {
            color: #2980b9;
            text-decoration: none;
            transition: all 0.3s ease;
        }

        .file-link:hover {
            color: #1a5276;
            text-decoration: underline;
        }

        .delete-btn {
            margin-left: 10px;
            background-color: #e74c3c;
            color: white;
            border: none;
            padding: 3px 8px;
            border-radius: 3px;
            cursor: pointer;
            font-size: 0.8rem;
            transition: background-color 0.3s ease;
        }

        .delete-btn:hover {
            background-color: #c0392b;
        }

        /* 进度条样式 */
        .progress-container {
            width: 100%;
            height: 8px;
            background-color: #e0e0e0;
            border-radius: 4px;
            margin-top: 15px;
            overflow: hidden;
            display: none; /* 默认隐藏 */
        }

        .progress-bar {
            height: 100%;
            background-color: #3498db;
            width: 0%;
            transition: width 0.3s ease;
        }

        /* 状态提示样式 */
        .status-message {
            margin-top: 15px;
            padding: 10px;
            border-radius: 4px;
            display: none;
        }

        .success {
            background-color: #dff0d8;
            color: #3c763d;
            border: 1px solid #d6e9c6;
        }

        .error {
            background-color: #f2dede;
            color: #a94442;
            border: 1px solid #ebccd1;
        }

        /* 空列表提示 */
        .empty-message {
            padding: 12px 20px;
            color: #7f8c8d;
            text-align: center;
        }
    </style>
</head>
<body>
    <!-- 添加返回按钮 -->
    <a href="../" class="back-button">返回</a>
    
    <h1>文件管理器</h1>

    <h2>上传文件</h2>
    <form id="uploadForm" class="upload-form" method="POST" enctype="multipart/form-data">
        <input type="file" name="file" id="fileInput" multiple>
        <input type="submit" value="上传">
        <div class="progress-container">
            <div class="progress-bar" id="progressBar"></div>
        </div>
        <div class="status-message" id="statusMessage"></div>
    </form>

    <h2>文件列表</h2>
    <ul id="fileList">
        <!-- 文件列表将通过JavaScript动态生成 -->
        <li class="empty-message">加载文件列表中...</li>
    </ul>

    <script>
        // 页面加载时就获取文件列表
        document.addEventListener('DOMContentLoaded', function() {
            refreshFileList();
        });

        // 文件上传进度
        const uploadForm = document.getElementById('uploadForm');
        const progressBar = document.getElementById('progressBar');
        const progressContainer = document.querySelector('.progress-container');
        const statusMessage = document.getElementById('statusMessage');
        const fileList = document.getElementById('fileList');

        uploadForm.addEventListener('submit', function(e) {
            e.preventDefault();
            
            const formData = new FormData(this);
            const xhr = new XMLHttpRequest();
            
            xhr.open('POST', '/upload', true);
            
            xhr.upload.addEventListener('progress', function(e) {
                if (e.lengthComputable) {
                    const percent = (e.loaded / e.total) * 100;
                    progressBar.style.width = percent + '%';
                    progressContainer.style.display = 'block';
                }
            });
            
            xhr.addEventListener('load', function() {
                progressContainer.style.display = 'none';
                if (xhr.status === 200) {
                    showStatus('文件上传成功！', 'success');
                    refreshFileList();
                } else {
                    showStatus('上传失败: ' + xhr.responseText, 'error');
                }
                // 重置表单
                uploadForm.reset();
                progressBar.style.width = '0%';
            });
            
            xhr.addEventListener('error', function() {
                progressContainer.style.display = 'none';
                showStatus('上传失败，请重试', 'error');
            });
            
            xhr.send(formData);
        });

        // 刷新文件列表 - 完全通过JavaScript生成，避免模板引擎问题
        function refreshFileList() {
            fetch('/file')
                .then(response => {
                    // 假设服务器返回JSON格式的文件列表
                    if (response.headers.get('content-type').includes('application/json')) {
                        return response.json();
                    }
                    // 如果返回的是HTML，则尝试从中提取文件信息
                    return response.text().then(html => {
                        // 这里是一个后备方案，根据实际返回的HTML结构调整
                        const parser = new DOMParser();
                        const doc = parser.parseFromString(html, 'text/html');
                        const items = doc.querySelectorAll('.file-item');
                        return Array.from(items).map(item => {
                            const link = item.querySelector('.file-link');
                            return link ? link.textContent : '';
                        }).filter(name => name);
                    });
                })
                .then(files => {
                    // 清空现有列表
                    fileList.innerHTML = '';
                    
                    // 检查文件列表是否为空
                    if (!files || files.length === 0) {
                        const emptyItem = document.createElement('li');
                        emptyItem.className = 'empty-message';
                        emptyItem.textContent = '暂无文件';
                        fileList.appendChild(emptyItem);
                        return;
                    }
                    
                    // 动态生成文件列表项
                    files.forEach(file => {
                        const listItem = document.createElement('li');
                        listItem.className = 'file-item';
                        
                        const fileLink = document.createElement('a');
                        fileLink.href = '/download/' + encodeURIComponent(file);
                        fileLink.className = 'file-link';
                        fileLink.textContent = file;
                        
                        const deleteBtn = document.createElement('button');
                        deleteBtn.className = 'delete-btn';
                        deleteBtn.textContent = '删除';
                        deleteBtn.setAttribute('data-filename', file);
                        
                        listItem.appendChild(fileLink);
                        listItem.appendChild(deleteBtn);
                        fileList.appendChild(listItem);
                    });
                })
                .catch(error => {
                    console.error('刷新文件列表失败:', error);
                    fileList.innerHTML = '<li class="empty-message">加载文件失败</li>';
                });
        }

        // 显示状态消息
        function showStatus(message, type) {
            statusMessage.textContent = message;
            statusMessage.className = 'status-message ' + type;
            statusMessage.style.display = 'block';
            
            // 3秒后隐藏消息
            setTimeout(() => {
                statusMessage.style.display = 'none';
            }, 3000);
        }

        // 为删除按钮添加事件监听
        fileList.addEventListener('click', function(e) {
            if (e.target.classList.contains('delete-btn')) {
                const filename = e.target.getAttribute('data-filename');
                if (confirm(`确定要删除文件 "${filename}" 吗？`)) {
                    deleteFile(filename);
                }
            }
        });

        // 删除文件函数
        function deleteFile(filename) {
            fetch(`/delete/${encodeURIComponent(filename)}`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                }
            })
            .then(response => response.json())
            .then(data => {
                if (data.status === 'success') {
                    showStatus('文件删除成功！', 'success');
                    refreshFileList();
                } else {
                    showStatus('删除失败: ' + data.message, 'error');
                }
            })
            .catch(error => {
                console.error('删除文件失败:', error);
                showStatus('删除失败，请重试', 'error');
            });
        }
    </script>
</body>
</html>

'''
@app.route('/file')
def fileindex():
    if 'username' in session:
        if (not (enable_file_download or enable_file_upload)) or ('username' not in session) or (UPLOAD_FOLDER == ''):
            return "服务端未允许文件传输", 403
        
        # 获取上传文件夹中的文件列表
        files = os.listdir(app.config['UPLOAD_FOLDER'])
        # 生成带删除按钮的文件列表项
        file_list = ''.join([f'<li class="file-item"><a href="/download/{file}" class="file-link">{file}</a><button class="delete-btn" data-filename="{file}">删除</button></li>' for file in files])
        rendered_html = HTML.replace('{% for file in files %}', '').replace('{% endfor %}', '').replace('{{ file }}', '')
        return rendered_html.replace('</ul>', f'{file_list}</ul>')
    else:
        return redirect('/login')

@app.route('/upload', methods=['POST'])
def upload_file():
    if (not enable_file_upload) or ('username' not in session) or (UPLOAD_FOLDER == ''):
        return jsonify({"status": "error", "message": "服务端未允许文件传输"}), 403
    
    if 'file' not in request.files:
        return jsonify({"status": "error", "message": "未找到文件"}), 400
    
    file = request.files['file']
    if file.filename == '':
        return jsonify({"status": "error", "message": "文件名不能为空"}), 400
    
    if file:
        try:
            file_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
            with open(file_path,'wb') as f:
                while chunk := file.stream.read(1024*1024):
                    f.write(chunk)
            return jsonify({"status": "success", "message": "文件上传成功"})
        except Exception as e:
            return jsonify({"status": "error", "message": f"上传失败: {str(e)}"}), 500

@app.route('/download/<filename>')
def download_file(filename):
    if (not enable_file_download) or ('username' not in session) or (UPLOAD_FOLDER == ''):
        return "服务端未允许文件传输", 403
    file_path = os.path.join(UPLOAD_FOLDER, filename)
    return send_file(
        file_path,
        mimetype='application/octet-stream',
        as_attachment=True,
        download_name=filename  # 指定下载时的文件名
    )

@app.route('/delete/<filename>', methods=['POST'])
def delete_file(filename):
    # 权限检查与文件上传保持一致
    if (not enable_file_upload) or ('username' not in session) or (UPLOAD_FOLDER == ''):
        return jsonify({"status": "error", "message": "没有删除权限"}), 403
    
    try:
        # 构建文件路径
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        
        # 安全检查：确保文件在上传目录内
        if not os.path.abspath(file_path).startswith(os.path.abspath(app.config['UPLOAD_FOLDER'])):
            return jsonify({"status": "error", "message": "无效的文件路径"}), 400
        
        # 检查文件是否存在
        if not os.path.exists(file_path) or not os.path.isfile(file_path):
            return jsonify({"status": "error", "message": "文件不存在"}), 404
        
        # 删除文件
        os.remove(file_path)
        return jsonify({"status": "success", "message": "文件已删除"})
    
    except Exception as e:
        return jsonify({"status": "error", "message": f"删除失败: {str(e)}"}), 500

# 添加文件列表JSON接口，用于前端异步刷新
@app.route('/file-list')
def file_list():
    if (not (enable_file_download or enable_file_upload)) or ('username' not in session) or (UPLOAD_FOLDER == ''):
        return jsonify([]), 403
    
    # 获取上传文件夹中的文件列表
    try:
        files = os.listdir(app.config['UPLOAD_FOLDER'])
        # 过滤掉目录，只保留文件
        files = [f for f in files if os.path.isfile(os.path.join(app.config['UPLOAD_FOLDER'], f))]
        return jsonify(files)
    except Exception as e:
        return jsonify([]), 500



# 图形化设计（Tkinter 界面）

# 系统托盘
class TrayApp:
    def __init__(self, root):
        self.root = root
        self.root.title("系统托盘包装")
        self.root.geometry("400x300")
        
        # 跟踪托盘图标状态
        self.tray_icon = None
        self.tray_running = False
        
        # 设置窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)
    
    def create_tray_icon(self):
        """创建系统托盘图标和菜单"""
        # 确保之前的图标已被正确清理
        if self.tray_icon:
            try:
                self.tray_icon.stop()
            except:
                pass
        
        # 创建一个简单的图标（使用PIL绘制）
        image = Image.new('RGB', (64, 64), color='white')
        draw = ImageDraw.Draw(image)
        draw.text((10, 25), "", fill='black')
        
        # 创建菜单
        menu = pystray.Menu(
            pystray.MenuItem("恢复", self.restore_window),
            pystray.MenuItem("退出", self.quit_app)
        )
        
        # 创建新的托盘图标
        self.tray_icon = pystray.Icon("TkApp", image, "Tkinter应用", menu)
        return self.tray_icon
    
    def on_close(self):
        """处理窗口关闭事件"""
        # 询问用户是隐藏到托盘还是关闭
        result = messagebox.askyesnocancel("关闭选项", "是否隐藏到系统托盘")
        
        if result is None:  # 取消操作
            return
        elif result:  # 隐藏到系统托盘
            self.root.withdraw()  # 隐藏窗口
            
            # 确保在新线程中启动托盘图标
            if not self.tray_running:
                def run_tray():
                    icon = self.create_tray_icon()
                    self.tray_running = True
                    icon.run()
                    self.tray_running = False
                
                threading.Thread(target=run_tray, daemon=True).start()
        else:  # 完全关闭
            self.quit_app()
    
    def restore_window(self, icon=None, item=None):
        """从系统托盘恢复窗口"""
        # 停止托盘图标
        if icon:
            icon.stop()
        
        # 更新状态
        self.tray_running = False
        
        # 显示窗口
        self.root.deiconify()
        self.root.lift()  # 把窗口提到最前面
    
    def quit_app(self, icon=None, item=None):
        """完全退出应用程序"""
        # 停止托盘图标
        if icon:
            icon.stop()
        
        # 更新状态
        self.tray_running = False
        
        # 销毁窗口
        self.root.destroy()

# 创建主窗口
root = tk.Tk()
rapp = TrayApp(root)
root.title('隧影')
root.geometry('800x350')
root.resizable(False, False)

# 左侧设置
ipport_var = tk.StringVar()
ipport_var.set(str(ipport))
ipportlab = tk.Entry(root, textvariable=ipport_var, relief="sunken")
ipportlab.pack()
ipportlab.place(x=50, y=50, width=200, height=20)

lab1 = tk.Label(root, text='投屏端口号', relief='flat')
lab1.pack()
lab1.place(x=280, y=50)

zhenlv_var = tk.StringVar()
zhenlv_var.set(str(fps))
zhenlvlab = tk.Entry(root, textvariable=zhenlv_var, relief="sunken")
zhenlvlab.pack()
zhenlvlab.place(x=50, y=100, width=200, height=20)

lab2 = tk.Label(root, text='帧率(-1为不限制)', relief='flat')
lab2.pack()
lab2.place(x=280, y=100)

quality_var = tk.StringVar()
quality_var.set(str(compression_quality))
qualitylab = tk.Entry(root, textvariable=quality_var, relief="sunken")
qualitylab.pack()
qualitylab.place(x=50, y=150, width=200, height=20)

lab3 = tk.Label(root, text='压缩质量(1-100)', relief='flat')
lab3.pack()
lab3.place(x=280, y=150)

ipv4_var = tk.BooleanVar()
ipv4_var.set(ipv4)
ipv4_button = tk.Checkbutton(root, text="使用IPv4而非ipv6", variable=ipv4_var)
ipv4_button.pack()
ipv4_button.place(x=50, y=200)

# ipv6地址显示框及获取函数
def get_ipv6_address():
    try:
        result = subprocess.run(['ipconfig'], capture_output=True, text=True, check=True)
        lines = result.stdout.split('\n')
        ipv6_addresses = []
        for line in lines:
            if '临时 IPv6 地址' in line:
                address = line.split(': ')[-1].strip()
                if address:
                    ipv6_addresses.append(address)
        if not ipv6_addresses:
            return '::1'
        return ipv6_addresses[0]
    except subprocess.CalledProcessError as e:
        print(f"Error occurred: {e}")
        return '::1'

def get_ipv4_address():
    try:
        result = subprocess.run(['ipconfig'], capture_output=True, text=True, check=True)
        lines = result.stdout.split('\n')
        ipv4_addresses = []
        for line in lines:
            if ('192.' in line) and ('IPv4 地址' in line):
                address = line.split(': ')[-1].strip()
                if address:
                    ipv4_addresses.append(address)
        if not ipv4_addresses:
            return '127.0.0.1'
        return ipv4_addresses[0]
    except subprocess.CalledProcessError as e:
        print(f"Error occurred: {e}")
        return '127.0.0.1'

ipv6_var = tk.StringVar()
ipv6lab = tk.Entry(root, textvariable=ipv6_var, state="readonly")
ipv6lab.pack()
ipv6lab.place(x=50, y=250, width=200, height=20)

lab4 = tk.Label(root, text='投屏访问地址', relief='flat')
lab4.pack()
lab4.place(x=280, y=250)

simulation_var = tk.BooleanVar()
simulation_var.set(enable_simulation)
simulation_button = tk.Checkbutton(root, text="远程控制", variable=simulation_var)
simulation_button.pack()
simulation_button.place(x=230, y=200)

enable_video_var = tk.BooleanVar()
enable_video_var.set(enable_video)
enable_video_button = tk.Checkbutton(root, text="投屏", variable=enable_video_var)
enable_video_button.pack()
enable_video_button.place(x=360, y=200)


# 右侧设置
username_var = tk.StringVar()
username_var.set(users[0])
usernamelab = tk.Entry(root, textvariable=username_var, relief="sunken")
usernamelab.pack()
usernamelab.place(x=450, y=50, width=200, height=20)

labr1 = tk.Label(root, text='用户名', relief='flat')
labr1.pack()
labr1.place(x=680, y=50)

password_var = tk.StringVar()
password_var.set(users[1])
passwordlab = tk.Entry(root, textvariable=password_var, relief="sunken")
passwordlab.pack()
passwordlab.place(x=450, y=100, width=200, height=20)

labr2 = tk.Label(root, text='密码', relief='flat')
labr2.pack()
labr2.place(x=680, y=100)

filesend_var = tk.StringVar()
filesend_var.set(UPLOAD_FOLDER)
filesendlab = tk.Entry(root, textvariable=filesend_var, relief="sunken")
filesendlab.pack()
filesendlab.place(x=450, y=150, width=200, height=20)

labr3 = tk.Label(root, text='传输用文件夹路径', relief='flat')
labr3.pack()
labr3.place(x=655, y=150)

def choose_flie_path(windowtitle = '选择文件夹'):
    global filesend_var
    filesend_var.set(filedialog.askdirectory(title = windowtitle))
    return None
buttonmain = tk.Button(root, text='选择', command=choose_flie_path)
buttonmain.pack()
buttonmain.place(x=680, y=175, width=50, height=20)

file_address_var = tk.StringVar()
file_addresslab = tk.Entry(root, textvariable=file_address_var, state="readonly")
file_addresslab.pack()
file_addresslab.place(x=450, y=250, width=200, height=20)

labr5 = tk.Label(root, text='文件访问地址', relief='flat')
labr5.pack()
labr5.place(x=680, y=250)

file_download_var = tk.BooleanVar()
file_download_var.set(enable_file_download)
file_download_button = tk.Checkbutton(root, text="文件下载", variable=file_download_var)
file_download_button.pack()
file_download_button.place(x=450, y=200)

file_upload_var = tk.BooleanVar()
file_upload_var.set(enable_file_upload)
file_upload_button = tk.Checkbutton(root, text="文件上传和删除", variable=file_upload_var)
file_upload_button.pack()
file_upload_button.place(x=630, y=200)

# 一键复制地址按钮
def copy_address():
    pyperclip.copy('投屏访问地址\n'+ipv6_var.get()+'\n文件访问地址\n'+file_address_var.get())
buttonmain = tk.Button(root, text='复制地址', command=copy_address)
buttonmain.pack()
buttonmain.place(x=450, y=300, width=100, height=40)

# 保存配置按钮
def save_config():
    with open('config.txt','w') as config_file:
        ipv4 = ipv4_var.get()
        ipport = int(ipport_var.get())        
        fps = int(zhenlv_var.get())  # 帧率 (fps)
        compression_quality = int(quality_var.get())  # 图像压缩质量 (1-100)
        compression_quality = round(95 * compression_quality / 100)
        enable_simulation = simulation_var.get()  # 是否启用远程控制和文件传输
        enable_file_download = file_download_var.get()
        enable_file_upload = file_upload_var.get()
        enable_video = enable_video_var.get()
        UPLOAD_FOLDER = filesend_var.get() # 文件传输文件夹路径
        users[0] = username_var.get()
        users[1] = password_var.get()
        config_file.write(f'''
fps = {fps}
compression_quality = {compression_quality}
ipv4 = {ipv4}
enable_simulation = {enable_simulation}
users = {users}
ipport = {ipport}
enable_video = {enable_video}
enable_file_download = {enable_file_download}
enable_file_upload = {enable_file_upload}
UPLOAD_FOLDER = '{UPLOAD_FOLDER.replace('\\','\\\\')}'
        '''.strip())
buttonmain = tk.Button(root, text='保存配置', command=save_config)
buttonmain.pack()
buttonmain.place(x=700, y=0, width=100, height=40)

# 主按钮
qidong = False
def main():
    global qidong, fps, sleeptime, compression_quality, enable_simulation, ip, ipport, users, UPLOAD_FOLDER, enable_file_download, enable_file_upload, enable_video, ipv4
    if not qidong:
        qidong = True
        ipv4 = ipv4_var.get()
        ip = '::'
        if ipv4:
            ip = '0.0.0.0'

        ipport = int(ipport_var.get())
        
        fps = int(zhenlv_var.get())  # 帧率 (fps)
        if fps == -1:
            sleeptime = 0
        else:
            sleeptime = 1 / fps

        compression_quality = int(quality_var.get())  # 图像压缩质量 (1-100)
        compression_quality = round(95 * compression_quality / 100)
        
        enable_simulation = simulation_var.get()  # 是否启用远程控制和文件传输
        enable_file_download = file_download_var.get()
        enable_file_upload = file_upload_var.get()
        enable_video = enable_video_var.get()

        UPLOAD_FOLDER = filesend_var.get() # 文件传输文件夹路径
        if UPLOAD_FOLDER != '':
            app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

        users[0] = username_var.get()
        users[1] = password_var.get()
        
        if not ipv4:
            ipv6_var.set('https://[' + get_ipv6_address() + ']:' + ipport_var.get())
            file_address_var.set(ipv6_var.get()+'/file')
        else:
            ipv6_var.set('https://' + get_ipv4_address() + ':' + ipport_var.get())
            file_address_var.set(ipv6_var.get()+'/file')

        # 启动 Flask 服务
        def run_flask():
            app.run(ssl_context='adhoc', host=ip, port=ipport, threaded=True)

        flask_thread = Thread(target=run_flask)
        flask_thread.daemon = True
        flask_thread.start()

buttonmain = tk.Button(root, text='开始投屏', command=main)
buttonmain.pack()
buttonmain.place(x=250, y=300, width=100, height=40)

#启动窗口
root.mainloop()
